/**
 * P6Spy
 * <p>
 * Copyright (C) 2002 P6Spy
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.p6spy.engine.wrapper;

import com.p6spy.engine.common.ResultSetInformation;
import com.p6spy.engine.common.StatementInformation;
import com.p6spy.engine.event.JdbcEventListener;

import java.sql.*;

/**
 * Statement 包装类
 * <p>
 * 此类为装饰者模式的具体实现，它包装了一个{@link StatementWrapper}对象，并在特定方法调用时通知一个{@link JdbcEventListener}。
 * <p>
 * 类似于Wrapper或Decorator设计模式，此类的方法默认情况下会委托给被包装的请求对象执行。
 * <p>
 * This implementation wraps a {@link StatementWrapper}  and notifies a {@link JdbcEventListener}
 * about certain method invocations.
 * <p>
 * This class implements the Wrapper or Decorator pattern. Methods default
 * to calling through to the wrapped request object.
 *
 * @see Statement
 */
public class StatementWrapper extends AbstractWrapper implements Statement {

    private static final String LINE_SEPARATOR = System.getProperty("line.separator");

    // 被包装的原始Statement对象
    private final Statement delegate;

    // 事件监听器，用于在SQL操作前后执行特定的回调
    protected final JdbcEventListener eventListener;

    // 包含Statement相关信息的对象
    private final StatementInformation statementInformation;

    /**
     * 创建一个新的StatementWrapper实例，包装给定的Statement对象，并关联事件监听器和Statement信息。
     *
     * @param delegate 要包装的原生Statement对象
     * @param statementInformation Statement的相关信息
     * @param eventListener 监听Statement操作的事件监听器
     */
    public static Statement wrap(Statement delegate, StatementInformation statementInformation, JdbcEventListener eventListener) {
        if (delegate == null) {
            return null;
        }
        return new StatementWrapper(delegate, statementInformation, eventListener);
    }

    /**
     * 构造函数，初始化包装的Statement对象、事件监听器和Statement信息。
     *
     * @param delegate 要包装的原生Statement对象
     * @param statementInformation Statement的相关信息
     * @param eventListener 监听Statement操作的事件监听器
     */
    protected StatementWrapper(Statement delegate, StatementInformation statementInformation, JdbcEventListener eventListener) {
        super(delegate);
        this.delegate = delegate;
        this.eventListener = eventListener;
        this.statementInformation = statementInformation;
        statementInformation.setStatement(delegate);
    }

    /**
     * 获得查询结果集。
     * 该方法会封装并返回一个ResultSet对象，这个对象是通过调用底层的delegate对象的getResultSet方法获得的。
     * 在获取结果集前后，会进行性能统计并通过事件监听器发送相关事件。
     *
     * @return ResultSet 查询结果集，封装了底层的结果集。
     * @throws SQLException 如果在获取结果集时发生任何SQL异常，则抛出。
     */
    @Override
    public ResultSet getResultSet() throws SQLException {
        SQLException e = null; // 用于捕捉在获取结果集过程中可能抛出的SQLException。
        long start = System.nanoTime(); // 记录开始获取结果集的时间点。
        try {
            // 尝试从底层delegate对象获取结果集，并对其进行封装。
            return ResultSetWrapper.wrap(delegate.getResultSet(), new ResultSetInformation(statementInformation), eventListener);
        } catch (SQLException sqle) {
            e = sqle; // 捕捉到SQLException，将其赋值给e。
            throw e; // 将捕捉到的SQLException向上抛出。
        } finally {
            // 无论是否成功获取结果集，都会执行finally块中的代码。
            // 记录获取结果集结束时间，并计算消耗的时间。
            eventListener.onAfterGetResultSet(statementInformation, System.nanoTime() - start, e); // 触发获取结果集后的事件监听。
        }
    }


    /**
     * 该方法是针对数据库查询操作的拦截重写，使用了P6Spy库进行监控和增强。
     *
     * @param sql 要执行的SQL查询语句
     * @return 包含查询结果的ResultSet对象
     * @throws SQLException 如果在执行查询时发生SQL异常，则抛出异常
     */
    @Override
    public ResultSet executeQuery(String sql) throws SQLException {
        // 设置当前Statement的信息，包括要执行的SQL查询
        statementInformation.setStatementQuery(sql);

        // 初始化一个变量来捕获可能发生的SQLException
        SQLException e = null;

        // 记录当前时间（纳秒级），用于计算查询耗时
        long start = System.nanoTime();

        try {
            // 在执行实际查询之前触发事件监听器，传递Statement信息与SQL查询
            eventListener.onBeforeExecuteQuery(statementInformation, sql);

            // 使用代理方式执行原始的查询方法，并用ResultSetWrapper包装返回的结果集
            // 这一步会将结果集与相关的监控信息关联起来
            return ResultSetWrapper.wrap(delegate.executeQuery(sql), new ResultSetInformation(statementInformation), eventListener);

        } catch (SQLException sqle) {
            // 当捕获到SQLException时，将其赋值给e以便在finally块中记录
            e = sqle;
            throw e; // 重新抛出异常以保证正常错误处理流程
        } finally {
            // 查询完成后触发事件监听器，报告查询执行完成的时间、SQL查询语句以及可能存在的异常信息
            eventListener.onAfterExecuteQuery(statementInformation, System.nanoTime() - start, sql, e);
        }
    }


    /**
     * 执行批处理命令。
     * 本方法会触发批处理执行前和执行后的事件监听器回调。
     *
     * @return int[] 包含每个执行语句的更新计数的数组；如果某个语句抛出异常，则该语句的更新计数为 Statement.SUCCESS_NO_INFO。
     * @throws SQLException 如果执行批处理时发生SQL异常。
     * @see java.sql.Statement#executeBatch()
     */
    @Override
    public int[] executeBatch() throws SQLException {
        // 初始化用于捕获的SQLException和更新计数数组
        SQLException e = null;
        long start = System.nanoTime();
        int[] updateCounts = null;
        try {
            // 执行批处理前的事件监听
            eventListener.onBeforeExecuteBatch(statementInformation);
            // 委派给底层的Statement执行批处理命令
            updateCounts = delegate.executeBatch();
            // 返回更新计数数组
            return updateCounts;
        } catch (SQLException sqle) {
            // 捕获SQLException，将其赋值给e，然后重新抛出
            e = sqle;
            throw e;
        } finally {
            // 执行批处理后的事件监听，无论是否抛出异常都会执行
            eventListener.onAfterExecuteBatch(statementInformation, System.nanoTime() - start, updateCounts, e);
        }
    }


    /**
     * 执行给定的SQL语句。
     * 该方法会记录执行前和执行后的事件，并通过事件监听器广播这些事件。
     * 如果执行过程中发生SQLException，将会被捕捉并抛出。
     *
     * @param sql 要执行的SQL语句。
     * @return 执行结果，如果委托对象的execute方法返回true，则返回true；否则返回false。
     * @throws SQLException 如果执行SQL语句时发生错误。
     */
    @Override
    public boolean execute(String sql) throws SQLException {
        // 设置当前语句信息为给定的SQL查询语句
        statementInformation.setStatementQuery(sql);
        SQLException e = null;
        long start = System.nanoTime();
        try {
            // 在执行SQL语句前触发事件监听器
            eventListener.onBeforeExecute(statementInformation, sql);
            // 委托给真实的Statement对象执行SQL语句
            return delegate.execute(sql);
        } catch (SQLException sqle) {
            e = sqle;
            // 捕获并抛出SQLException
            throw e;
        } finally {
            // 不论执行成功或失败，最后都会触发事件监听器
            eventListener.onAfterExecute(statementInformation, System.nanoTime() - start, sql, e);
        }
    }


    /**
     * 执行给定的SQL语句。
     * 该方法会记录执行的SQL语句，监听执行的前后事件，并处理可能发生的SQLException。
     *
     * @param sql 要执行的SQL语句。
     * @param autoGeneratedKeys 指定是否需要检索自动生成的键。
     * @return 如果执行的是查询，则返回true；否则返回false。
     * @throws SQLException 如果执行过程中发生SQLException。
     */
    @Override
    public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
        // 设置待执行的SQL查询语句
        statementInformation.setStatementQuery(sql);
        SQLException e = null;
        long start = System.nanoTime();
        try {
            // 执行前的事件监听
            eventListener.onBeforeExecute(statementInformation, sql);
            // 委托给真实的Statement对象执行SQL语句
            return delegate.execute(sql, autoGeneratedKeys);
        } catch (SQLException sqle) {
            e = sqle;
            // 捕获并抛出SQLException
            throw e;
        } finally {
            // 执行后的事件监听，包括执行时间和服务端返回的异常信息
            eventListener.onAfterExecute(statementInformation, System.nanoTime() - start, sql, e);
        }
    }


    /**
     * 执行给定的SQL语句，并指定影响的列索引。
     * 该方法会触发执行前和执行后的事件监听。
     *
     * @param sql 要执行的SQL查询语句。
     * @param columnIndexes 指定要影响的列的索引数组。
     * @return 如果执行成功返回true，否则返回false。
     * @throws SQLException 如果执行SQL语句时发生错误。
     */
    @Override
    public boolean execute(String sql, int[] columnIndexes) throws SQLException {
        // 设置待执行的SQL语句信息
        statementInformation.setStatementQuery(sql);
        SQLException e = null;
        long start = System.nanoTime();
        try {
            // 执行前的事件监听
            eventListener.onBeforeExecute(statementInformation, sql);
            // 委托给真实的Statement对象执行SQL语句
            return delegate.execute(sql, columnIndexes);
        } catch (SQLException sqle) {
            e = sqle;
            throw e;
        } finally {
            // 执行后的事件监听，无论执行成功或失败都会触发
            eventListener.onAfterExecute(statementInformation, System.nanoTime() - start, sql, e);
        }
    }


    /**
     * 执行给定的SQL语句。
     * 该方法会记录执行前和执行后的事件，以及执行时是否遇到了SQLException。
     *
     * @param sql 要执行的SQL查询语句。
     * @param columnNames 列名数组，用于指示执行的查询应该返回哪些列。
     * @return boolean 表示SQL语句是否成功执行。
     * @throws SQLException 如果执行过程中遇到了SQL异常。
     */
    @Override
    public boolean execute(String sql, String[] columnNames) throws SQLException {
        // 设置待执行的SQL语句信息
        statementInformation.setStatementQuery(sql);
        SQLException e = null;
        long start = System.nanoTime();
        try {
            // 执行前的事件通知
            eventListener.onBeforeExecute(statementInformation, sql);
            // 委托给真实的Statement对象执行SQL语句
            return delegate.execute(sql, columnNames);
        } catch (SQLException sqle) {
            e = sqle;
            throw e;
        } finally {
            // 执行后的事件通知，包括执行时间和服务端返回的异常信息
            eventListener.onAfterExecute(statementInformation, System.nanoTime() - start, sql, e);
        }
    }


    /**
     * 根据提供的SQL语句执行更新查询，并返回受影响的行数。
     * 此方法覆盖了来自父类的executeUpdate方法，
     * 在执行前后会触发事件监听器，以便记录执行统计信息和异常情况。
     *
     * @param sql SQL更新语句，用于在数据库上执行更新操作。
     * @return 受影响的行数，表示SQL更新操作所影响的数据行数量。
     * @throws SQLException 如果在执行SQL过程中发生错误，将抛出此异常。
     */
    @Override
    public int executeUpdate(String sql) throws SQLException {
        // 设置当前执行的SQL查询到statementInformation中，以便跟踪
        statementInformation.setStatementQuery(sql);

        // 初始化可能发生的SQLException引用和执行开始时的系统纳秒时间
        SQLException e = null;
        long start = System.nanoTime();

        // 初始化记录受影响行数的变量
        int rowCount = 0;

        try {
            // 在执行SQL更新前触发监听器事件
            eventListener.onBeforeExecuteUpdate(statementInformation, sql);

            // 调用委托对象的executeUpdate方法执行SQL更新，并获取受影响的行数
            rowCount = delegate.executeUpdate(sql);

            return rowCount;
        } catch (SQLException sqle) {
            // 捕获并保存执行过程中可能发生的SQLException
            e = sqle;

            // 重新抛出捕获到的SQLException，保持异常传递
            throw e;
        } finally {
            eventListener.onAfterExecuteUpdate(statementInformation, System.nanoTime() - start, sql, rowCount, e);
        }
    }

    @Override
    public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
        statementInformation.setStatementQuery(sql);
        SQLException e = null;
        long start = System.nanoTime();
        int rowCount = 0;
        try {
            eventListener.onBeforeExecuteUpdate(statementInformation, sql);
            rowCount = delegate.executeUpdate(sql, autoGeneratedKeys);
            return rowCount;
        } catch (SQLException sqle) {
            e = sqle;
            throw e;
        } finally {
            eventListener.onAfterExecuteUpdate(statementInformation, System.nanoTime() - start, sql, rowCount, e);
        }
    }

    @Override
    public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
        statementInformation.setStatementQuery(sql);
        SQLException e = null;
        long start = System.nanoTime();
        int rowCount = 0;
        try {
            eventListener.onBeforeExecuteUpdate(statementInformation, sql);
            rowCount = delegate.executeUpdate(sql, columnIndexes);
            return rowCount;
        } catch (SQLException sqle) {
            e = sqle;
            throw e;
        } finally {
            eventListener.onAfterExecuteUpdate(statementInformation, System.nanoTime() - start, sql, rowCount, e);
        }
    }

    @Override
    public int executeUpdate(String sql, String[] columnNames) throws SQLException {
        statementInformation.setStatementQuery(sql);
        SQLException e = null;
        long start = System.nanoTime();
        int rowCount = 0;
        try {
            eventListener.onBeforeExecuteUpdate(statementInformation, sql);
            rowCount = delegate.executeUpdate(sql, columnNames);
            return rowCount;
        } catch (SQLException sqle) {
            e = sqle;
            throw e;
        } finally {
            eventListener.onAfterExecuteUpdate(statementInformation, System.nanoTime() - start, sql, rowCount, e);
        }
    }

    /**
     * 向当前的批处理中添加一个SQL语句。
     * 如果已经存在一个SQL语句，则将新的SQL语句追加到现有的批处理中。
     * 在添加SQL语句到批处理前后，会触发事件监听器的相关事件。
     *
     * @param sql 要添加到批处理的SQL语句。
     * @throws SQLException 如果在添加SQL语句到批处理时发生数据库错误。
     */
    @Override
    public void addBatch(String sql) throws SQLException {
        // 如果当前还没有SQL语句，则设置为新的SQL语句；否则，将新的SQL语句追加到现有的SQL语句之后
        if (statementInformation.getStatementQuery() == null) {
            statementInformation.setStatementQuery(sql);
        } else {
            statementInformation.setStatementQuery(sql + LINE_SEPARATOR + statementInformation.getStatementQuery());
        }

        SQLException e = null;
        long start = System.nanoTime();
        try {
            // 在添加SQL语句到批处理之前触发事件
            eventListener.onBeforeAddBatch(statementInformation, sql);
            delegate.addBatch(sql); // 实际添加SQL语句到批处理
        } catch (SQLException sqle) {
            e = sqle;
            throw e; // 如果添加过程中发生SQLException，抛出该异常
        } finally {
            // 不论是否成功，都在添加后触发事件
            eventListener.onAfterAddBatch(statementInformation, System.nanoTime() - start, sql, e);
        }
    }


    @Override
    public void close() throws SQLException {
        SQLException e = null;
        try {
            delegate.close();
        } catch (SQLException sqle) {
            e = sqle;
            throw e;
        } finally {
            eventListener.onAfterStatementClose(statementInformation, e);
        }
    }

    @Override
    public int getMaxFieldSize() throws SQLException {
        return delegate.getMaxFieldSize();
    }

    @Override
    public void setMaxFieldSize(int max) throws SQLException {
        delegate.setMaxFieldSize(max);
    }

    @Override
    public int getMaxRows() throws SQLException {
        return delegate.getMaxRows();
    }

    @Override
    public void setMaxRows(int max) throws SQLException {
        delegate.setMaxRows(max);
    }

    @Override
    public void setEscapeProcessing(boolean enable) throws SQLException {
        delegate.setEscapeProcessing(enable);
    }

    @Override
    public int getQueryTimeout() throws SQLException {
        return delegate.getQueryTimeout();
    }

    @Override
    public void setQueryTimeout(int seconds) throws SQLException {
        delegate.setQueryTimeout(seconds);
    }

    @Override
    public void cancel() throws SQLException {
        delegate.cancel();
    }

    @Override
    public SQLWarning getWarnings() throws SQLException {
        return delegate.getWarnings();
    }

    @Override
    public void clearWarnings() throws SQLException {
        delegate.clearWarnings();
    }

    @Override
    public void setCursorName(String name) throws SQLException {
        delegate.setCursorName(name);
    }

    @Override
    public int getUpdateCount() throws SQLException {
        return delegate.getUpdateCount();
    }

    @Override
    public boolean getMoreResults() throws SQLException {
        return delegate.getMoreResults();
    }

    @Override
    public void setFetchDirection(int direction) throws SQLException {
        delegate.setFetchDirection(direction);
    }

    @Override
    public int getFetchDirection() throws SQLException {
        return delegate.getFetchDirection();
    }

    @Override
    public void setFetchSize(int rows) throws SQLException {
        delegate.setFetchSize(rows);
    }

    @Override
    public int getFetchSize() throws SQLException {
        return delegate.getFetchSize();
    }

    @Override
    public int getResultSetConcurrency() throws SQLException {
        return delegate.getResultSetConcurrency();
    }

    @Override
    public int getResultSetType() throws SQLException {
        return delegate.getResultSetType();
    }

    @Override
    public void clearBatch() throws SQLException {
        delegate.clearBatch();
    }

    @Override
    public Connection getConnection() throws SQLException {
        return delegate.getConnection();
    }

    @Override
    public boolean getMoreResults(int current) throws SQLException {
        return delegate.getMoreResults(current);
    }

    @Override
    public ResultSet getGeneratedKeys() throws SQLException {
        return delegate.getGeneratedKeys();
    }

    @Override
    public int getResultSetHoldability() throws SQLException {
        return delegate.getResultSetHoldability();
    }

    @Override
    public boolean isClosed() throws SQLException {
        return delegate.isClosed();
    }

    @Override
    public void setPoolable(boolean poolable) throws SQLException {
        delegate.setPoolable(poolable);
    }

    @Override
    public boolean isPoolable() throws SQLException {
        return delegate.isPoolable();
    }

    @Override
    public void closeOnCompletion() throws SQLException {
        delegate.closeOnCompletion();
    }

    @Override
    public boolean isCloseOnCompletion() throws SQLException {
        return delegate.isCloseOnCompletion();
    }

}
